﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework;

namespace client.app
{
    public class BloomSettings
    {
        public readonly string Name;
        public readonly float BloomThreshold;
        public readonly float BlurAmount;
        public readonly float BloomIntensity;
        public readonly float BaseIntensity;
        public readonly float BloomSaturation;
        public readonly float BaseSaturation;

        public BloomSettings(string name, float bloomThreshold, float blurAmount, float bloomIntensity, float baseIntensity, float bloomSaturation, float baseSaturation)
        {
            Name = name;
            BloomThreshold = bloomThreshold;
            BlurAmount = blurAmount;
            BloomIntensity = bloomIntensity;
            BaseIntensity = baseIntensity;
            BloomSaturation = bloomSaturation;
            BaseSaturation = baseSaturation;
        }

        public static BloomSettings[] PresetSettings =
        {
            //                Name           Thresh  Blur Bloom  Base  BloomSat BaseSat
            new BloomSettings("Default",     0.25f,  4,   1.25f, 1,    1,       1),
            new BloomSettings("Soft",        0,      3,   1,     1,    1,       1),
            new BloomSettings("Desaturated", 0.5f,   8,   2,     1,    0,       1),
            new BloomSettings("Saturated",   0.25f,  4,   2,     1,    2,       0),
            new BloomSettings("Blurry",      0,      2,   1,     0.1f, 1,       1),
            new BloomSettings("Subtle",      0.5f,   2,   1,     1,    1,       1),
        };
    }

    public class Bloom
    {
        public enum IntermediateBuffer
        {
            PreBloom,
            BlurredHorizontally,
            BlurredBothWays,
            FinalResult,
        }

        IntermediateBuffer _showBuffer = IntermediateBuffer.BlurredBothWays;
        public IntermediateBuffer ShowBuffer
        {
            get { return _showBuffer; }
            set { _showBuffer = value; }
        }

        BloomSettings _settings = BloomSettings.PresetSettings[5];
        public BloomSettings Settings
        {
            get { return _settings; }
            set { _settings = value; }
        }

        SpriteBatch _spriteBatch = null;

        Effect _bloomExtractEffect = null;
        Effect _bloomCombineEffect = null;
        Effect _gaussianBlurEffect = null;

        RenderTarget2D _renderTarget1 = null;
        RenderTarget2D _renderTarget2 = null;
        RenderTarget2D _inputRenderTarget = null;
        RenderTarget2D _outputRenderTarget = null;

        public Bloom(RenderTarget2D inputRenderTarget, RenderTarget2D outputRenderTarget)
        {
            _inputRenderTarget = inputRenderTarget;
            _outputRenderTarget = outputRenderTarget;

            _spriteBatch = new SpriteBatch(MainGame.Instance.GraphicsDevice);

            _bloomExtractEffect = MainGame.Instance.Content.Load<Effect>("effect/BloomExtract");
            _bloomCombineEffect = MainGame.Instance.Content.Load<Effect>("effect/BloomCombine");
            _gaussianBlurEffect = MainGame.Instance.Content.Load<Effect>("effect/GaussianBlur");

            var pp = MainGame.Instance.GraphicsDevice.PresentationParameters;
            var format = pp.BackBufferFormat;

            int width = _inputRenderTarget.Width;
            int height = _inputRenderTarget.Height;

            width /= 2;
            height /= 2;

            _renderTarget1 = new RenderTarget2D(MainGame.Instance.GraphicsDevice, width, height, false, format, DepthFormat.None);
            _renderTarget2 = new RenderTarget2D(MainGame.Instance.GraphicsDevice, width, height, false, format, DepthFormat.None);

            MainGame.Instance.GraphicsDevice.SetRenderTarget(_inputRenderTarget);
        }

        public void Draw()
        {
            MainGame.Instance.GraphicsDevice.SamplerStates[1] = SamplerState.LinearClamp;
            _bloomExtractEffect.Parameters["BloomThreshold"].SetValue(Settings.BloomThreshold);

            DrawFullscreenQuad(_inputRenderTarget, _renderTarget1, _bloomExtractEffect, IntermediateBuffer.PreBloom);

            SetBlurEffectParameters(1.0f / (float)_renderTarget1.Width, 0);
            DrawFullscreenQuad(_renderTarget1, _renderTarget2, _gaussianBlurEffect, IntermediateBuffer.BlurredHorizontally);

            SetBlurEffectParameters(0, 1.0f / (float)_renderTarget1.Height);

            DrawFullscreenQuad(_renderTarget2, _renderTarget1, _gaussianBlurEffect, IntermediateBuffer.BlurredBothWays);

            MainGame.Instance.GraphicsDevice.SetRenderTarget(_outputRenderTarget);

            var parameters = _bloomCombineEffect.Parameters;
            parameters["BloomIntensity"].SetValue(Settings.BloomIntensity);
            parameters["BaseIntensity"].SetValue(Settings.BaseIntensity);
            parameters["BloomSaturation"].SetValue(Settings.BloomSaturation);
            parameters["BaseSaturation"].SetValue(Settings.BaseSaturation);

            MainGame.Instance.GraphicsDevice.Textures[1] = _inputRenderTarget;

            DrawFullscreenQuad(_renderTarget1, _inputRenderTarget.Width, _inputRenderTarget.Height, _bloomCombineEffect, IntermediateBuffer.FinalResult);
        }

        void DrawFullscreenQuad(Texture2D texture, RenderTarget2D renderTarget, Effect effect, IntermediateBuffer currentBuffer)
        {
            MainGame.Instance.GraphicsDevice.SetRenderTarget(renderTarget);
            DrawFullscreenQuad(texture, renderTarget.Width, renderTarget.Height, effect, currentBuffer);
        }
        void DrawFullscreenQuad(Texture2D texture, int width, int height, Effect effect, IntermediateBuffer currentBuffer)
        {
            if (_showBuffer < currentBuffer)
            {
                effect = null;
            }
            _spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Opaque, null, null, null, effect);
            _spriteBatch.Draw(texture, new Rectangle(0, 0, width, height), Color.White);
            _spriteBatch.End();
        }
        void SetBlurEffectParameters(float dx, float dy)
        {
            EffectParameter weightsParameter, offsetsParameter;
            weightsParameter = _gaussianBlurEffect.Parameters["SampleWeights"];
            offsetsParameter = _gaussianBlurEffect.Parameters["SampleOffsets"];
            int sampleCount = weightsParameter.Elements.Count;
            float[] sampleWeights = new float[sampleCount];
            Vector2[] sampleOffsets = new Vector2[sampleCount];
            sampleWeights[0] = ComputeGaussian(0);
            sampleOffsets[0] = new Vector2(0);
            float totalWeights = sampleWeights[0];
            for (int i = 0; i < sampleCount / 2; i++)
            {
                float weight = ComputeGaussian(i + 1);

                sampleWeights[i * 2 + 1] = weight;
                sampleWeights[i * 2 + 2] = weight;

                totalWeights += weight * 2;

                float sampleOffset = i * 2 + 1.5f;

                Vector2 delta = new Vector2(dx, dy) * sampleOffset;

                sampleOffsets[i * 2 + 1] = delta;
                sampleOffsets[i * 2 + 2] = -delta;
            }

            for (int i = 0; i < sampleWeights.Length; i++)
            {
                sampleWeights[i] /= totalWeights;
            }

            weightsParameter.SetValue(sampleWeights);
            offsetsParameter.SetValue(sampleOffsets);
        }
        float ComputeGaussian(float n)
        {
            float theta = Settings.BlurAmount;

            return (float)((1.0 / Math.Sqrt(2 * Math.PI * theta)) *
                           Math.Exp(-(n * n) / (2 * theta * theta)));
        }
    }
}
